﻿/**
 * Some helpers for macros.
 * 
 * Author: Caleb Leak
 * Date:   12.30.2010
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Windows.Forms;
using System.Threading;

namespace ControllerToMouse
{
    /**
     * Keys that can be used in macros.
     * 
     */

    public enum Key
    {
        W, A, S, D, BACKSPACE, SPACE, U, ENTER, TAB,
        F1, F2, F3, F4, F5, F6, F9, F10,
        NUM_1, NUM_2, NUM_3, NUM_4, NUM_5, NUM_6, NUM_7, NUM_8, NUM_9, NUM_0,
        CAPS, ESC, L_CTRL
    }

    /**
     * Controls input driven by macros.
     * 
     */

    public class InputController
    {
        ASCIIEncoding encoder;

        /**
         * Keyboard scan codes for numbers.
         * 
         */

        int[] numCodes =
        {
            0x45,
            0x16,
            0x1E,
            0x26,
            0x25,
            0x2E,
            0x36,
            0x3D,
            0x8E,
            0x45
        };

        /**
         * Keyboard scan codes for letters.
         * 
         */

        int[] asciiScanCodes = 
        {
            0x1c,
            0x32,
            0x21,
            0x23,
            0x24,
            0x2B,
            0x34,
            0x33,
            0x43,
            0x3B,
            0x42,
            0x4B,
            0x3A,
            0x31,
            0x44,
            0x4D,
            0x15,
            0x2D,
            0x1B,
            0x2C,
            0x3C,
            0x2A,
            0x1D,
            0x22,
            0x2C,
            0x1A
        };

        /**
         * Key code and state information.
         * 
         */

        class KeyDetails
        {
            /**
             * Constructs a new key entry.
             * 
             * @param key the programmer friendly key enum.
             * @param vkCode the virtual keycode of the key.
             * @param scanCode the keyboard scan code of the key.
             */

            public KeyDetails(Key key, byte vkCode, byte scanCode)
            {
                this.key = key;
                this.vkCode = vkCode;
                this.scanCode = scanCode;
            }

            public Key key;
            public bool isDown;

            public byte vkCode;
            public byte scanCode;
        }

        Dictionary<Key, KeyDetails> keyMap;

        const int KEYEVENTF_KEYUP = 0x02;

        /**
         * Constructs an input controller populated by a default set of keys.
         * 
         */

        public InputController()
        {
            keyMap = new Dictionary<Key, KeyDetails>();

            keyMap.Add(Key.TAB, new KeyDetails(Key.TAB, 0x09, 0x0F));

            keyMap.Add(Key.A, new KeyDetails(Key.A, 0x41, 0x1E));
            keyMap.Add(Key.D, new KeyDetails(Key.D, 0x44, 0x20));
            keyMap.Add(Key.W, new KeyDetails(Key.W, 0x57, 0x11));
            keyMap.Add(Key.S, new KeyDetails(Key.S, 0x53, 0x1F));
            keyMap.Add(Key.U, new KeyDetails(Key.U, 0x55, 0x16));
            keyMap.Add(Key.SPACE, new KeyDetails(Key.SPACE, 0x20, 0x39));
            keyMap.Add(Key.BACKSPACE, new KeyDetails(Key.BACKSPACE, 0x08, 0x0E));
            keyMap.Add(Key.ENTER, new KeyDetails(Key.ENTER, 0x0D, 0x1C));

            keyMap.Add(Key.CAPS, new KeyDetails(Key.CAPS, (byte)Keys.CapsLock, 0x58));
            keyMap.Add(Key.L_CTRL, new KeyDetails(Key.L_CTRL, (byte)Keys.LControlKey, 0x14));
            keyMap.Add(Key.ESC, new KeyDetails(Key.ESC, (byte)Keys.Escape, 0x76));

            keyMap.Add(Key.F1, new KeyDetails(Key.F1, 0x70, 0x3B));
            keyMap.Add(Key.F2, new KeyDetails(Key.F2, 0x71, 0x3C));
            keyMap.Add(Key.F3, new KeyDetails(Key.F3, 0x72, 0x3D));
            keyMap.Add(Key.F4, new KeyDetails(Key.F4, 0x73, 0x3E));
            keyMap.Add(Key.F5, new KeyDetails(Key.F5, 0x74, 0x3F));
            keyMap.Add(Key.F6, new KeyDetails(Key.F6, 0x75, 0x40));
            keyMap.Add(Key.F9, new KeyDetails(Key.F9, 0x78, 0x01));
            keyMap.Add(Key.F10, new KeyDetails(Key.F10, 0x79, 0x09));

            keyMap.Add(Key.NUM_0, new KeyDetails(Key.NUM_0, 0x30, 0x0B));
            keyMap.Add(Key.NUM_1, new KeyDetails(Key.NUM_1, 0x31, 0x02));
            keyMap.Add(Key.NUM_2, new KeyDetails(Key.NUM_2, 0x32, 0x03));
            keyMap.Add(Key.NUM_3, new KeyDetails(Key.NUM_3, 0x33, 0x04));
            keyMap.Add(Key.NUM_4, new KeyDetails(Key.NUM_4, 0x34, 0x05));
            keyMap.Add(Key.NUM_5, new KeyDetails(Key.NUM_5, 0x35, 0x06));
            keyMap.Add(Key.NUM_6, new KeyDetails(Key.NUM_6, 0x36, 0x07));
            keyMap.Add(Key.NUM_7, new KeyDetails(Key.NUM_7, 0x37, 0x08));
            keyMap.Add(Key.NUM_8, new KeyDetails(Key.NUM_8, 0x38, 0x09));
            keyMap.Add(Key.NUM_9, new KeyDetails(Key.NUM_9, 0x39, 0x0A));

            encoder = new ASCIIEncoding();
        }

        /**
         * Presses a single key.
         * 
         * @param key the character that the key is associated with.
         */

        public void pressKey(char key)
        {
            char[] charArray = new char[1];
            charArray[0] = key;
            int code = encoder.GetBytes(charArray)[0];
            typeKey(code);
        }

        /**
         * Types a key by pressing it, holding it, and the releasing it.
         * 
         * @param code the scan code of the key.
         */

        public void typeKey(int code)
        {
            int A = 0x41;

            int vkCode = code;
            int scanCode = 0;

            if (code == 0x20)
            {
                // Special case for spaces
                scanCode = 0x29;
            }
            else if (code >= 0x30 && code <= 0x39)
            {
                // Special case for numbers

                scanCode = numCodes[code - 0x30];
            }
            else
            {
                int index = code - A;

                // Filter out character we don't know how to print
                if (index < 0 || index >= 26)
                    return;

                scanCode = (byte)asciiScanCodes[index];
            }

            keybd_event((byte)vkCode, (byte)scanCode, 0, 0);
            Thread.Sleep(10);
            keybd_event((byte)vkCode, (byte)scanCode, KEYEVENTF_KEYUP, 0);
            Thread.Sleep(10);
        }

        /**
         * Presses (and holds) a key down.
         * 
         * @param key the scancode of the key to be pressed.
         */

        public void pressKey(Key key)
        {

            keyMap[key].isDown = true;
            keybd_event(keyMap[key].vkCode, keyMap[key].scanCode, 0, 0);
        }

        /**
         * Releases a given key.
         * 
         * @param key the scancode of the key to be released.
         */

        public void releaseKey(Key key)
        {
            keyMap[key].isDown = false;
            keybd_event(keyMap[key].vkCode, keyMap[key].scanCode, KEYEVENTF_KEYUP, 0);
        }

        /**
         * Releases all keys which are currently being held down.
         * 
         */

        public void releaseAllKeys()
        {
            foreach (Key key in keyMap.Keys)
            {
                releaseKey(key);
            }
        }

        /**
         * Released and then presses once more all keys which are currently
         * being held.
         * 
         */

        public void repressAllKeys()
        {
            foreach (Key key in keyMap.Keys)
            {
                bool pressed = keyMap[key].isDown;
                if (pressed)
                {
                    releaseKey(key);
                    keyMap[key].isDown = true;
                }
            }

            foreach (Key key in keyMap.Keys)
            {
                if(keyMap[key].isDown)
                    pressKey(key);
            }
        }

        const int MOUSEEVENTF_ABSOLUTE = 0x8000;
        const int MOUSEEVENTF_LEFTDOWN = 0x2;
        const int MOUSEEVENTF_LEFTUP = 0x4;
        const int MOUSEEVENTF_MIDDLEDOWN = 0x20;
        const int MOUSEEVENTF_MIDDLEUP = 0x40;
        const int MOUSEEVENTF_MOVE = 0x1;
        const int MOUSEEVENTF_RIGHTDOWN = 0x8;
        const int MOUSEEVENTF_RIGHTUP = 0x10;

        /**
         * Presses (and holds) the left mouse button at the given location.
         * 
         * @param the x screen coordinate, in pixels, where the mouse should be
         *        pressed.
         */

        public void mouseDown(int x, int y)
        {
            mouseMove(x, y);
            mouse_event(MOUSEEVENTF_LEFTDOWN,
                0, 0, 0, 0);
        }

        /**
         * Release the left mouse button.
         * 
         */

        public void mouseUp()
        {
            mouse_event(MOUSEEVENTF_LEFTUP,
                0, 0, 0, 0);
        }

        /**
         * Moves the mouse to the given screen coordinates.
         * 
         * @param x the x coordinate to move the mouse to.
         * @param y the y coordinate to move the mouse to.
         */

        public void mouseMove(int x, int y)
        {
            SetCursorPos(x, y);
            Thread.Sleep(30);
        }

        /**
         * Retrieves the x screen coordinate of the mouse's currently location.
         * 
         * @return the x cordinate, in pixels, of the mouse.
         */

        public int getMouseX()
        {
            return System.Windows.Forms.Cursor.Position.X;
        }

        /**
         * Retrieves the y screen coordinate of the mouse's currently location.
         * 
         * @return the x cordinate, in pixels, of the mouse.y
         */

        public int getMouseY()
        {
            return System.Windows.Forms.Cursor.Position.Y;
        }

        /**
         * Scrolls the mouse wheel at the given location.
         * 
         * @param x the x coordinate of the mouse where the scrolling should
         *        take place.
         * @param y the y coordinate of the mouse where the scrolling should
         *        take place.
         * @param amount the positive or negative amount to scroll.
         */

        public void scroll(int x, int y, int amount)
        {
            mouseMove(x, y);
            mouse_event(0x800, 0, 0, amount, 0);
        }

        [DllImport("user32.dll", EntryPoint = "keybd_event", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern void keybd_event(byte vk, byte scan, int flags, int extrainfo);

        [DllImport("user32.dll", EntryPoint = "mouse_event", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern void mouse_event(int flags, int dx, int dy, int data, int extrainfo);


        private static Int32 WM_KEYDOWN = 0x100;
        private static Int32 WM_KEYUP = 0x101;

        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll", SetLastError = true)]
        static extern bool PostMessage(IntPtr hWnd, int Msg, System.Windows.Forms.Keys wParam, int lParam);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

        [DllImport("user32.dll")]
        static extern bool SetCursorPos(int X, int Y);

        /**
         * Sends a key to the given window.
         * 
         */

        public static void sendKey(IntPtr hWnd, System.Windows.Forms.Keys key)
        {
            PostMessage(hWnd, WM_KEYDOWN, key, 0);
        }
    }
}
